package org.gazzax.labs.solrwithmarc.index.analysis;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.util.TokenFilterFactory;

/**
 * A filter factory that detects date ranges and explodes them in order to
 * allows fast range queries.
 * 
 * @author agazzarini
 * @since 1.0
 */
public class DetectAndExpandRangeFilterFactory extends TokenFilterFactory 
{	
	interface FilterState
	{
		boolean incrementToken() throws IOException;
	}
	
	final class FilterImpl extends TokenFilter 
	{
		private final CharTermAttribute termAtt = (CharTermAttribute) addAttribute(CharTermAttribute.class);
		private final PositionIncrementAttribute posIncrAtt = (PositionIncrementAttribute)addAttribute(PositionIncrementAttribute.class);
		private final OffsetAttribute offsetAtt = (OffsetAttribute)addAttribute(OffsetAttribute.class);
		
		private final List<Integer> years = new ArrayList<Integer>();

		final FilterState detecting = new FilterState()
		{
			@Override
			public boolean incrementToken() throws IOException 
			{
				while (FilterImpl.this.input.incrementToken()) 
				{
					int indexOfSeparator = indexOfRangeSeparator();
					if ((indexOfSeparator == -1)) { continue; }
					
					int startYear = detectStartYear(indexOfSeparator);
					if (startYear == -1) { continue; }
					
					int endYear = detectEndYear(indexOfSeparator);					
					if (endYear == -1) 
					{ 
						createYearToken(startYear);
						return false;
					}

					for (int i = startYear; i <= endYear; i++) 
					{
						years.add(i);
					}
					break;
				}
				
				state = expanding;
				return state.incrementToken();
			}
			
			private int detectStartYear(final int indexOfSeparator) 
			{
				int index = indexOfSeparator;
				while (index != 0)
				{
					char character = termAtt.charAt(index);
					if (Character.isDigit(character))
					{
						if (index >= 3 && Character.isDigit(termAtt.charAt(index - 1)) && Character.isDigit(termAtt.charAt(index - 2)) && Character.isDigit(termAtt.charAt(index - 3)))
						{
							return Integer.parseInt(termAtt.subSequence(index - 3, index + 1).toString());
						}
					}
					index--;
				}
				return -1;
			}

			private int detectEndYear(final int indexOfSeparator) 
			{
				int index = indexOfSeparator;
				while (index != termAtt.length() - 3)
				{
					char character = termAtt.charAt(index);
					if (Character.isDigit(character))
					{
						if (index <= (termAtt.length() - 4) && Character.isDigit(termAtt.charAt(index + 1)) && Character.isDigit(termAtt.charAt(index + 2)) && Character.isDigit(termAtt.charAt(index + 3)))
						{
							return Integer.parseInt(termAtt.subSequence(index, index + 4).toString());
						}
					}
					index++;
				}
				return -1;
			}

			private int indexOfRangeSeparator() 
			{
				for (int index = 0; index < termAtt.length(); index++) 
				{
					if ((termAtt.charAt(index) == '-')) 
					{
						return index;
					}
				}
				return -1;
			}	
		};

		final FilterState expanding = new FilterState()
		{
			@Override
			public boolean incrementToken() throws IOException 
			{
				if (!years.isEmpty()) 
				{
					createYearToken(years.remove(0));
					return true;
				}
				return false;
			}
		};
		
		private FilterState state = detecting;
		
		/**
		 * Builds a new filter.
		 * 
		 * @param input the input {@link TokenStream}.
		 */
		public FilterImpl(final TokenStream input) 
		{
			super(input);
			state = detecting;
		}
		
		@Override
		public final boolean incrementToken() throws IOException 
		{
			return state.incrementToken();
		}
		
		private void createYearToken(final Integer year)
		{
			termAtt.setEmpty();
			termAtt.append(String.valueOf(year));
			posIncrAtt.setPositionIncrement(1);
			offsetAtt.setOffset(0, 4);
		}
	}
	
	@Override
	public TokenStream create(final TokenStream tokenStream) 
	{
		return new FilterImpl(tokenStream);
	}
}